x
1
1
#Mini-portable shell - newx
#Mini-portable shell - newdeclare function getText(element: Element): string;
declare function getText(fn: Function): string;
declare function setText(element: Element, text: string): void;
declare function elem(tag: string): HTMLElement;
declare function elem(tag: string, style: {}, parent?: Element): HTMLElement;
declare function elem(tag: string, parent: Element): HTMLElement;
declare function elem(elem: HTMLElement, style: {}, parent?: Element): HTMLElement;
declare function createFrame(style?: {}):
{ global: Window; document: Document; iframe: HTMLIFrameElement; };
declare function loadMod(
options: {
/** module script */
eval: string;
/** module path to emulate */
path?: string;
/** style or class name for the injected iframe (not needed for headless) */
style?: {} | string;
/** scope to inject, or a function to create such scope using the existing global of the injected iframe */
scope?: {} | ((global: any) => {});
/** whether to show the iframe (false to delete iframe immediately) */
ui?: boolean;
}): { global: any; document: Document; iframe: HTMLIFrameElement; };
declare module loadMod {
export interface LoadedResult {
global: any;
document: Document;
iframe: HTMLIFrameElement
}
}function getText(obj) {
if (typeof obj === 'function') {
var result = /\/\*(\*(?!\/)|[^*])*\*\//m.exec(obj+'')[0];
if (result) result = result.slice(2, result.length-2);
return result;
}
else if (/^SCRIPT$/i.test(obj.tagName)) {
if ('text' in obj)
return obj.text;
else
return obj.innerHTML;
}
else if (/^STYLE$/i.test(obj.tagName)) {
if ('text' in obj)
return obj.text;
else if (obj.styleSheet)
return obj.styleSheet.cssText;
else
return obj.innerHTML;
}
else if ('textContent' in obj) {
return obj.textContent;
}
else if (/^INPUT$/i.test(obj.tagName)) {
return obj.value;
}
else {
var result = obj.innerText;
if (result) {
// IE fixes
result = result.replace(/\<BR\s*\>/g, '\n').replace(/\r\n/g, '\n');
}
return result || '';
}
}
function setText(obj, text) {
if (/^SCRIPT$/i.test(obj.tagName)) {
if ('text' in obj)
obj.text = text;
else
obj.innerHTML = text;
}
else if (/^STYLE$/i.test(obj.tagName)) {
if ('text' in obj)
obj.text = text;
else if (obj.styleSheet)
obj.styleSheet.cssText = text;
else
obj.innerHTML = text;
}
else if ('textContent' in obj) {
obj.textContent = text;
}
else if (/^INPUT$/i.test(obj.tagName)) {
obj.value = text;
}
else {
obj.innerText = text;
}
}
function elem(tag, style, parent) {
var e = tag.tagName ? tag : this.document.createElement(tag);
if (!parent && style && style.tagName) {
parent = style;
style = null;
}
if (style) {
if (typeof style === 'string') {
setText(e, style);
}
else {
for (var k in style) if (style.hasOwnProperty(k)) {
if (k === 'text') {
setText(e, style[k]);
}
else if (k === 'className') {
e.className = style[k];
}
else if (!(k in e.style) && k in e) {
e[k] = style[k];
}
else {
try {
e.style[k] = style[k];
}
catch (err) {
try {
if (typeof console !== 'undefined' && typeof console.error === 'function')
console.error(e.tagName+'.style.'+k+'='+style[k]+': '+err.message);
}
catch (whatevs) {
alert(e.tagName+'.style.'+k+'='+style[k]+': '+err.message);
}
}
}
}
}
}
if (parent) {
try {
parent.appendChild(e);
}
catch (error) {
throw new Error(error.message+' adding '+e.tagName+' to '+parent.tagName);
}
}
return e;
}
function createFrame(style) {
if (!style)
style = {
position: 'absolute',
left: 0, top: 0,
width: '100%', height: '100%',
border: 'none',
src: 'about:blank'
};
var ifr = this.elem('iframe', style, this.document.body);
var ifrwin = ifr.contentWindow || ifr.window;
var ifrdoc = ifrwin.document;
if (ifrdoc.open) ifrdoc.open();
ifrdoc.write(
'<!'+'doctype html'+'>'+
'<'+'html'+'>'+
'<'+'head'+'><'+'style'+'>'+
'body,html{margin:0;padding:0;border:none;height:100%;}'+
'*,*:before,*:after{box-sizing:inherit;}'+
'html{box-sizing:border-box;}'+
'</'+'style'+'>\n'+
'<'+'body'+'><'+'body'+'>'+
'</'+'html'+'>');
if (ifrdoc.close) ifrdoc.close();
ifrwin.elem = elem;
return {
document: ifrdoc,
global: ifrwin,
iframe: ifr
};
}
function loadMod(options) {
var style = options.style;
if (!options.ui) {
style = { display: 'none' };
}
else if (typeof style === 'string') {
style = { className: style, display: 'none' };
}
var frame = this.createFrame(style);
var frameFunction = frame.global.Function;
if (options.scope) {
var scope = typeof options.scope === 'function' ? (options.scope)(frame.global) : options.scope;
for (var k in scope) if (scope.hasOwnProperty(k)) {
frame.global[k] = scope[k];
}
}
if (options.eval) {
var exportsInScope = scope && 'exports' in scope;
var evalArgNames = exportsInScope ? [] : ['exports'];
var evalArgs = exportsInScope ? [] : [{}];
if (scope) {
for (var k in scope) if (scope.hasOwnProperty(k)) {
evalArgNames.push(k);
evalArgs.push(scope[k]);
}
}
if (!options.ui) {
var allowedGlobals = {
setTimeout: 1, setInterval: 1, clearTimeout: 1, clearInterval: 1,
eval: 1,
console: 1,
undefined: 1,
Array: 1, Date: 1, Function: 1, String: 1, Boolean: 1, Number: 1,
Infinity: 1, NaN: 1, isNaN: 1, isFinite: 1, parseInt: 1, parseFloat: 1,
escape: 1, unescape: 1,
Int32Array: 1, Int8Array: 1, Int16Array: 1,
UInt32Array: 1, UInt8Array: 1, UInt8ClampedArray: 1, UInt16Array: 1,
Float32Array: 1, Float64Array: 1, ArrayBuffer: 1,
Math: 1, JSON: 1, RegExp: 1,
Error: 1, SyntaxError: 1, EvalError: 1, RangeError: 1, ReferenceError: 1,
toString: 1, toJSON: 1, toValue: 1,
Map: 1
};
var hiddenKeys = {};
// normal properties
for (var k in frame.global) {
if (scope && scope.hasOwnProperty(k)) continue;
if (allowedGlobals.hasOwnProperty(k)) continue;
evalArgNames.push(k);
hiddenKeys[k] = 1;
}
// non-enumerable properties directly on global
if (Object.getOwnPropertyNames) {
var props = Object.getOwnPropertyNames(frame.global);
for (var i = 0; i < props.length; i++) {
if (scope && scope.hasOwnProperty(props[i])) continue;
if (allowedGlobals.hasOwnProperty(props[i])) continue;
if (hiddenKeys.hasOwnProperty(props[i])) continue;
evalArgNames.push(props[i]);
}
// non-enumerable properties on global's prototype
if (frame.global.constructor
&& frame.global.constructor.prototype
&& frame.global.constructor.prototype != Object
&& frame.global.constructor.prototype != Object.prototype) {
props = Object.getOwnPropertyNames(frame.global.constructor.prototype);
for (var i = 0; i < props.length; i++) {
if (scope && scope.hasOwnProperty(props[i])) continue;
if (allowedGlobals.hasOwnProperty(props[i])) continue;
if (hiddenKeys.hasOwnProperty(props[i])) continue;
evalArgNames.push(props[i]);
}
}
}
}
evalArgNames.push(
options.path ? options.eval + '\nreturn exports; //# sourceURL=' + options.path : options.eval);
var evalFn = frameFunction.apply(frame.global, evalArgNames);
var modExports = evalFn.apply(frame.global, evalArgs);
return {
document: frame.document,
global: frame.global,
iframe: frame.iframe,
exports: modExports
};
}
return frame;
}function bootUI(document, window, elem) {
elem(document.body, {
background: 'royalblue'
});
var header = elem('h2', { text: 'Loading...' }, document.body);
return {
loaded: function() {
setText(header, 'Loaded.');
}
};
}function earlyBoot() {
document.write(
'<'+'style'+'>'+
'*{display:none;background:white;color:white;}'+
'html,body,iframe{display:block;}'+
'</'+'style'+'>'+
(document.body ? '' : '<body>'));
var bootFrame = createFrame();
bootFrame.global.elem = elem;
var bootAPI = bootUI(bootFrame.document, bootFrame.global, function elemProxy(a,b,c) { return bootFrame.global.elem(a,b,c); });
bootFrame.api = bootAPI;
var uniqueKey = deriveUniqueKey(location);
var shellLoaderInstance = null;
var shellLoadInterval = setInterval(function() {
shellLoaderInstance = shellLoaderInstance ? shellLoaderInstance.continueLoading() : shellLoader(uniqueKey, document);
}, 100);
window.onload = function() {
clearInterval(shellLoadInterval);
(shellLoaderInstance || shellLoader(uniqueKey, document, bootFrame)).finishLoading();
};
function deriveUniqueKey(locationSeed) {
var key = (locationSeed + '').split('?')[0].split('#')[0].toLowerCase();
var posIndexTrail = key.search(/\/index\.html$/);
if (posIndexTrail>0) key = key.slice(0, posIndexTrail);
if (key.charAt(0) === '/')
key = key.slice(1);
if (key.slice(-1) === '/')
key = key.slice(0, key.length - 1);
return smallHash(key) + '-' + smallHash(key.slice(1) + 'a');
function smallHash(key) {
for (var h=0, i=0; i < key.length; i++) {
h = Math.pow(31, h + 31 / key.charCodeAt(i));
h -= h | 0;
}
return (h * 2000000000) | 0;
}
}
}window.onerror = function onerror() {
var msg = [];
for (var i = 0; i < arguments.length; i++) {
var a = arguments[i];
if (a && (typeof a === 'object')) {
if (a.stack) {
msg.push(a.stack);
}
else {
var msg1 = [];
for (var k in a) {
var r = a[k];
if (typeof r === 'function' || (typeof r === 'object' && !r)) continue;
msg1.push(k+':'+r);
}
msg.push(msg1.join(', '));
}
}
else {
msg.push(a===null ? 'null' : a);
}
}
alert(msg.join('\n'));
}function shellLoader(uniqueKey: string, document: Document, boot: shellLoader.BootModuleAPI): shellLoader.ContinueLoading {
var driveMount = persistence.bootMount(uniqueKey, document);
return continueLoading();
function continueLoading(): shellLoader.ContinueLoading {
driveMount.continueLoading();
return { continueLoading, finishLoading };
}
function finishLoading() {
driveMount.finishLoading(drive => {
// EVERYTHING READY!
console.log(window['dbgDrive'] = drive);
elem(boot.document.body, {
background: 'white',
color: 'black'
});
var coolLoadedTitle = elem('h1', {
text: 'Finished.'
}, boot.document.body);
});
}
}
module shellLoader {
export interface BootModuleAPI extends loadMod.LoadedResult {
api: any;
}
export interface ContinueLoading {
continueLoading(): ContinueLoading;
finishLoading();
}
}module persistence {
function getIndexedDB() {
try {
return typeof indexedDB === 'undefined' || typeof indexedDB.open !== 'function' ? null : indexedDB;
}
catch (error) {
return null;
}
}
export module attached.indexedDB {
export var name = 'indexedDB';
export function detect(uniqueKey: string, callback: (detached: Drive.Detached) => void): void {
try {
detectCore(uniqueKey, callback);
}
catch (error) {
callback(null);
}
}
function detectCore(uniqueKey: string, callback: (detached: Drive.Detached) => void): void {
var indexedDBInstance = getIndexedDB();
if (!indexedDBInstance) {
callback(null);
return;
}
var dbName = uniqueKey || 'portabled';
var openRequest = indexedDBInstance.open(dbName, 1);
openRequest.onerror = (errorEvent) => callback(null);
openRequest.onupgradeneeded = createDBAndTables;
openRequest.onsuccess = (event) => {
var db: IDBDatabase = openRequest.result;
try {
var transaction = db.transaction(['files', 'metadata']);
// files mentioned here, but not really used to detect
// broken multi-store transaction implementation in Safari
transaction.onerror = (errorEvent) => callback(null);
var metadataStore = transaction.objectStore('metadata');
var filesStore = transaction.objectStore('files');
var editedUTCRequest = metadataStore.get('editedUTC');
}
catch (getStoreError) {
callback(null);
return;
}
if (!editedUTCRequest) {
callback(null);
return;
}
editedUTCRequest.onerror = (errorEvent) => {
var detached = new IndexedDBDetached(db, null);
callback(detached);
};
editedUTCRequest.onsuccess = (event) => {
var result: MetadataData = editedUTCRequest.result;
var detached = new IndexedDBDetached(db, result && typeof result.value === 'number' ? result.value : null);
callback(detached);
};
}
function createDBAndTables() {
var db: IDBDatabase = openRequest.result;
var filesStore = db.createObjectStore('files', { keyPath: 'path' });
var metadataStore = db.createObjectStore('metadata', { keyPath: 'property' })
}
}
class IndexedDBDetached implements Drive.Detached {
constructor(
private _db: IDBDatabase,
public timestamp: number) {
}
applyTo(mainDrive: Drive, callback: Drive.Detached.CallbackWithShadow): void {
var transaction = this._db.transaction(['files', 'metadata'], 'readwrite');
var metadataStore = transaction.objectStore('metadata');
var filesStore = transaction.objectStore('files');
var countRequest = filesStore.count();
countRequest.onerror = (errorEvent) => {
console.error('Could not count files store.');
callback(null);
};
countRequest.onsuccess = (event) => {
var storeCount: number = countRequest.result;
var cursorRequest = filesStore.openCursor();
cursorRequest.onerror = (errorEvent) => callback(null);
// to cleanup any files which content is the same on the main drive
var deleteList: string[] = [];
var anyLeft = false;
var processedCount = 0;
cursorRequest.onsuccess = (event) => {
var cursor: IDBCursor = cursorRequest.result;
if (!cursor) {
// cleaning up files whose content is duplicating the main drive
if (anyLeft) {
for (var i = 0; i < deleteList.length; i++) {
filesStore['delete'](deleteList[i]);
}
}
else {
filesStore.clear();
metadataStore.clear();
}
callback(new IndexedDBShadow(this._db, this.timestamp));
return;
}
if (callback.progress)
callback.progress(processedCount, storeCount);
processedCount++;
var result: FileData = (<any>cursor).value;
if (result && result.path) {
var existingContent = mainDrive.read(result.path);
if (existingContent === result.content) {
deleteList.push(result.path);
}
else {
mainDrive.timestamp = this.timestamp;
mainDrive.write(result.path, result.content);
anyLeft = true;
}
}
cursor['continue']();
}; // cursorRequest.onsuccess
}; // countRequest.onsuccess
}
purge(callback: Drive.Detached.CallbackWithShadow): void {
var transaction = this._db.transaction(['files', 'metadata'], 'readwrite');
var filesStore = transaction.objectStore('files');
filesStore.clear();
var metadataStore = transaction.objectStore('metadata');
metadataStore.clear();
callback(new IndexedDBShadow(this._db, -1));
}
}
class IndexedDBShadow implements Drive.Shadow {
constructor(private _db: IDBDatabase, public timestamp: number) {
}
write(file: string, content: string) {
var transaction = this._db.transaction(['files', 'metadata'], 'readwrite');
var filesStore = transaction.objectStore('files');
var metadataStore = transaction.objectStore('metadata');
// no file deletion here: we need to keep account of deletions too!
var fileData: FileData = {
path: file,
content: content,
state: null
};
var putFile = filesStore.put(fileData);
var md: MetadataData = {
property: 'editedUTC',
value: Date.now()
};
metadataStore.put(md);
}
}
interface FileData {
path: string;
content: string;
state: string;
}
interface MetadataData {
property: string;
value: any;
}
}
}module persistence {
function getLocalStorage() {
return typeof localStorage === 'undefined' || typeof localStorage.length !== 'number' ? null : localStorage;
}
// is it OK&
export module attached.localStorage {
export var name = 'localStorage';
export function detect(uniqueKey: string, callback: (detached: Drive.Detached) => void): void {
var localStorageInstance = getLocalStorage();
if (!localStorageInstance) {
callback(null);
return;
}
var access = new LocalStorageAccess(localStorageInstance, uniqueKey);
var dt = new LocalStorageDetached(access);
callback(dt);
}
class LocalStorageAccess {
private _cache: { [key: string]: string; } = {};
constructor(private _localStorage: Storage, private _prefix: string) {
}
get (key: string): string {
var k = this._expandKey(key);
var r = this._localStorage.getItem(k);
return r;
}
set(key: string, value: string): void {
var k = this._expandKey(key);
return this._localStorage.setItem(k, value);
}
remove(key: string): void {
var k = this._expandKey(key);
return this._localStorage.removeItem(k);
}
keys(): string[] {
var result: string[] = [];
var len = this._localStorage.length;
for (var i = 0; i < len; i++) {
var str = this._localStorage.key(i);
if (str.length > this._prefix.length && str.slice(0, this._prefix.length) === this._prefix)
result.push(str.slice(this._prefix.length));
}
return result;
}
private _expandKey(key: string): string {
var k: string;
if (!key) {
k = this._prefix;
}
else {
k = this._cache[key];
if (!k)
this._cache[key] = k = this._prefix + key;
}
return k;
}
}
class LocalStorageDetached implements Drive.Detached {
timestamp: number = 0;
constructor(private _access: LocalStorageAccess) {
var timestampStr = this._access.get('*timestamp');
if (timestampStr && timestampStr.charAt(0)>='0' && timestampStr.charAt(0)<='9') {
try {
this.timestamp = parseInt(timestampStr);
}
catch (parseError) {
}
}
}
applyTo(mainDrive: Drive, callback: Drive.Detached.CallbackWithShadow): void {
var keys = this._access.keys();
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
if (k.charAt(0)==='/') {
var value = this._access.get(k);
mainDrive.write(k, value);
}
}
var shadow = new LocalStorageShadow(this._access, mainDrive.timestamp);
callback(shadow);
}
purge(callback: Drive.Detached.CallbackWithShadow): void {
var keys = this._access.keys();
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
if (k.charAt(0)==='/') {
var value = this._access.remove(k);
}
}
var shadow = new LocalStorageShadow(this._access, this.timestamp);
callback(shadow);
}
}
class LocalStorageShadow implements Drive.Shadow {
constructor(private _access: LocalStorageAccess, public timestamp: number) {
}
write(file: string, content: string) {
this._access.set(file, content);
this._access.set('*timestamp', <any>this.timestamp);
}
}
}
} module persistence {
function getOpenDatabase() {
return typeof openDatabase !== 'function' ? null : openDatabase;
}
export module attached.webSQL {
export var name = 'webSQL';
export function detect(uniqueKey: string, callback: (detached: Drive.Detached) => void): void {
var openDatabaseInstance = getOpenDatabase();
if (!openDatabaseInstance) {
callback(null);
return;
}
var dbName = uniqueKey || 'portabled';
var db = openDatabase(
dbName, // name
1, // version
'Portabled virtual filesystem data', // displayName
1024 * 1024); // size
// upgradeCallback?
db.readTransaction(
transaction => {
transaction.executeSql(
'SELECT value from "*metadata" WHERE name=\'editedUTC\'',
[],
(transaction, result) => {
var editedValue: number = null;
if (result.rows && result.rows.length === 1) {
var editedValueStr = result.rows.item(0).value;
if (typeof editedValueStr === 'string') {
try {
editedValue = parseInt(editedValueStr);
}
catch (error) {
// unexpected value for the timestamp, continue as if no value found
}
}
else if (typeof editedValueStr === 'number') {
editedValue = editedValueStr;
}
}
callback(new WebSQLDetached(db, editedValue || 0, true));
},
(transaction, sqlError) => {
// no data
callback(new WebSQLDetached(db, 0, false));
});
},
sqlError=> {
// failed to load
callback(null);
});
}
class WebSQLDetached implements Drive.Detached {
constructor(
private _db: Database,
public timestamp: number,
private _metadataTableIsValid: boolean) {
}
applyTo(mainDrive: Drive, callback: Drive.Detached.CallbackWithShadow): void {
this._db.readTransaction(
transaction => listAllTables(
transaction,
tables => {
var ftab = getFilenamesFromTables(tables);
this._applyToWithFiles(transaction, ftab, mainDrive, callback);
},
sqlError => {
reportSQLError('Failed to list tables for the webSQL database.', sqlError);
callback(new WebSQLShadow(this._db, this.timestamp, this._metadataTableIsValid));
}),
sqlError => {
reportSQLError('Failed to open read transaction for the webSQL database.', sqlError);
callback(new WebSQLShadow(this._db, this.timestamp, this._metadataTableIsValid));
});
}
purge(callback: Drive.Detached.CallbackWithShadow): void {
this._db.transaction(
transaction => listAllTables(
transaction,
tables => {
this._purgeWithTables(transaction, tables, callback);
},
sqlError => {
reportSQLError('Failed to list tables for the webSQL database.', sqlError);
callback(new WebSQLShadow(this._db, 0, false));
}),
sqlError => {
reportSQLError('Failed to open read-write transaction for the webSQL database.', sqlError);
callback(new WebSQLShadow(this._db, 0, false));
});
}
private _applyToWithFiles(transaction: SQLTransaction, ftab: { file: string; table: string; }[], mainDrive: Drive, callback: Drive.Detached.CallbackWithShadow): void {
if (!ftab.length) {
callback(new WebSQLShadow(this._db, this.timestamp, this._metadataTableIsValid));
return;
}
var reportedFileCount = 0;
var completeOne = () => {
reportedFileCount++;
if (reportedFileCount === ftab.length) {
callback(new WebSQLShadow(this._db, this.timestamp, this._metadataTableIsValid));
}
};
var applyFile = (file: string, table: string) => {
transaction.executeSql(
'SELECT * FROM "' + table + '"',
[],
(transaction, result) => {
if (result.rows.length) {
var row = result.rows.item(0);
if (row.value === null)
mainDrive.write(file, null);
else if (typeof row.value === 'string')
mainDrive.write(file, fromSqlText(row.value));
}
completeOne();
},
sqlError => {
completeOne();
});
};
for (var i = 0; i < ftab.length; i++) {
applyFile(ftab[i].file, ftab[i].table);
}
}
private _purgeWithTables(transaction: SQLTransaction, tables: string[], callback: Drive.Detached.CallbackWithShadow) {
if (!tables.length) {
callback(new WebSQLShadow(this._db, 0, false));
return;
}
var droppedCount = 0;
var completeOne = () => {
droppedCount++;
if (droppedCount === tables.length) {
callback(new WebSQLShadow(this._db, 0, false));
}
};
for (var i = 0; i < tables.length; i++) {
transaction.executeSql(
'DROP TABLE "' + tables[i] + '"',
[],
(transaction, result) => {
completeOne();
},
(transaction, sqlError) => {
reportSQLError('Failed to drop table for the webSQL database.', sqlError);
completeOne();
});
}
}
}
class WebSQLShadow implements Drive.Shadow {
private _cachedUpdateStatementsByFile: { [name: string]: string; } = {};
private _closures = {
updateMetadata: (transaction: SQLTransaction) => this._updateMetadata(transaction)
};
constructor(private _db: Database, public timestamp: number, private _metadataTableIsValid: boolean) {
}
write(file: string, content: string) {
if (content || typeof content === 'string') {
this._updateCore(file, content);
}
else {
this._dropFileTable(file);
}
}
private _updateCore(file: string, content: string) {
var updateSQL = this._cachedUpdateStatementsByFile[file];
if (!updateSQL) {
var tableName = mangleDatabaseObjectName(file);
updateSQL = this._createUpdateStatement(file, tableName);
}
this._db.transaction(
transaction => {
transaction.executeSql(
updateSQL,
['content', content],
this._closures.updateMetadata,
(transaction, sqlError) => this._createTableAndUpdate(transaction, file, tableName, updateSQL, content));
},
sqlError => {
reportSQLError('Transaction failure updating file "' + file + '".', sqlError);
});
}
private _createTableAndUpdate(transaction: SQLTransaction, file: string, tableName: string, updateSQL: string, content: string) {
if (!tableName)
tableName = mangleDatabaseObjectName(file);
transaction.executeSql(
'CREATE TABLE "' + tableName + '" (name PRIMARY KEY, value)',
[],
(transaction, result) => {
transaction.executeSql(
updateSQL,
['content', content],
this._closures.updateMetadata,
(transaction, sqlError) => {
reportSQLError('Failed to update table "' + tableName + '" for file "' + file + '" after creation.', sqlError);
});
},
(transaction, sqlError) => {
reportSQLError('Failed to create a table "' + tableName + '" for file "' + file + '".', sqlError);
});
}
private _dropFileTable(file: string) {
var tableName = mangleDatabaseObjectName(file);
this._db.transaction(
transaction => {
transaction.executeSql(
'DROP TABLE "' + tableName + '"',
[],
this._closures.updateMetadata,
(transaction, sqlError) => {
reportSQLError('Failed to drop table "' + tableName + '" for file "' + file + '".', sqlError);
});
},
sqlError => {
reportSQLError('Transaction failure dropping table "' + tableName + '" for file "' + file + '".', sqlError);
});
}
private _updateMetadata(transaction: SQLTransaction) {
var updateMetadataSQL = 'INSERT OR REPLACE INTO "*metadata" VALUES (?,?)';
transaction.executeSql(
updateMetadataSQL,
['editedUTC', this.timestamp],
(transaction, result) => { }, // TODO: generate closure statically
(transaction, error) => {
transaction.executeSql(
'CREATE TABLE "*metadata" (name PRIMARY KEY, value)',
[],
(transaction, result) => {
transaction.executeSql(updateMetadataSQL, [], () => { }, () => { });
},
(transaction, sqlError) => {
reportSQLError('Failed to update metadata table after creation.', sqlError);
});
});
}
private _createUpdateStatement(file: string, tableName: string): string {
return this._cachedUpdateStatementsByFile[file] =
'INSERT OR REPLACE INTO "' + tableName + '" VALUES (?,?)';
}
}
function mangleDatabaseObjectName(name: string): string {
// no need to polyfill btoa, if webSQL exists
if (name.toLowerCase() === name)
return name;
else
return '=' + btoa(name);
}
function unmangleDatabaseObjectName(name: string): string {
if (!name || name.charAt(0) === '*') return null;
if (name.charAt(0) !== '=') return name;
try {
return atob(name.slice(1));
}
catch (error) {
return name;
}
}
export function listAllTables(
transaction: SQLTransaction,
callback: (tables: string[]) => void,
errorCallback: (sqlError: SQLError) => void) {
transaction.executeSql(
'SELECT tbl_name from sqlite_master WHERE type=\'table\'',
[],
(transaction, result) => {
var tables: string[] = [];
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
var table = row.tbl_name;
if (!table || (table[0] !== '*' && table.charAt(0) !== '=' && table.charAt(0) !== '/')) continue;
tables.push(row.tbl_name);
}
callback(tables);
},
(transaction, sqlError) => errorCallback(sqlError));
}
function getFilenamesFromTables(tables: string[]) {
var filenames: { table: string; file: string; }[] = [];
for (var i = 0; i < tables.length; i++) {
var file = unmangleDatabaseObjectName(tables[i]);
if (file)
filenames.push({ table: tables[i], file: file });
}
return filenames;
}
function toSqlText(text: string) {
if (text.indexOf('\u00FF') < 0 && text.indexOf('\u0000') < 0) return text;
return text.replace(/\u00FF/g, '\u00FFf').replace(/\u0000/g, '\u00FF0');
}
function fromSqlText(sqlText: string) {
if (sqlText.indexOf('\u00FF') < 0 && sqlText.indexOf('\u0000') < 0) return sqlText;
return sqlText.replace(/\u00FFf/g, '\u00FF').replace(/\u00FF0/g, '\u0000');
}
function reportSQLError(message: string, sqlError: SQLError);
function reportSQLError(sqlError: SQLError);
function reportSQLError(message, sqlError?) {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
if (sqlError)
console.error(message, sqlError);
else
console.error(sqlError);
}
}
}
}module persistence.dom {
export class CommentHeader {
header: string;
contentOffset: number;
contentLength: number;
constructor(public node: Comment) {
var headerLine: string;
var content: string;
if (typeof node.substringData === 'function'
&& typeof node.length === 'number') {
var chunkSize = 128;
if (node.length >= chunkSize) {
// TODO: cut chunks off the start and look for newlines
var headerChunks: string[] = [];
while (headerChunks.length * chunkSize < node.length) {
var nextChunk = node.substringData(headerChunks.length * chunkSize, chunkSize);
var posEOL = nextChunk.search(/\r|\n/);
if (posEOL < 0) {
headerChunks.push(nextChunk);
continue;
}
this.header = headerChunks.join('') + nextChunk.slice(0, posEOL);
this.contentOffset = this.header.length + 1; // if header is separated by a single CR or LF
if (posEOL === nextChunk.length - 1) { // we may have LF part of CRLF in the next chunk!
if (nextChunk.charAt(nextChunk.length - 1) === '\r'
&& node.substringData((headerChunks.length + 1) * chunkSize, 1) === '\n')
this.contentOffset++;
}
else if (nextChunk.slice(posEOL, posEOL + 2) === '\r\n') {
this.contentOffset++;
}
this.contentLength = node.length - this.contentOffset;
return;
}
this.header = headerChunks.join('');
this.contentOffset = this.header.length;
this.contentLength = node.length - content.length;
return;
}
}
var wholeCommentText = node.nodeValue;
var posEOL = wholeCommentText.search(/\r|\n/);
if (posEOL < 0) {
this.header = wholeCommentText;
this.contentOffset = wholeCommentText.length;
this.contentLength = wholeCommentText.length - this.contentOffset;
return;
}
this.contentOffset = wholeCommentText.slice(posEOL, posEOL + 2) === '\r\n' ?
posEOL + 2 : // ends with CRLF
posEOL + 1; // ends with singular CR or LF
this.header = wholeCommentText.slice(0, posEOL),
this.contentLength = wholeCommentText.length - this.contentOffset
}
}
}module persistence.dom {
export class DOMDrive implements Drive {
private _byPath: { [path: string]: DOMFile; } = {};
public timestamp: number;
constructor(
private _totals: DOMTotals,
files: DOMFile[],
private _document: DOMDrive.DocumentSubset) {
this.timestamp = this._totals ? this._totals.timestamp : 0;
for (var i = 0; i < files.length; i++) {
this._byPath[files[i].path] = files[i];
}
}
files(): string[] {
if (typeof Object.keys === 'string') {
var result = Object.keys(this._byPath);
}
else {
var result: string[] = [];
for (var k in this._byPath) if (this._byPath.hasOwnProperty(k)) {
result.push(k);
}
}
result.sort();
return result;
}
read(file: string): string {
var file = normalizePath(file);
var f = this._byPath[file];
if (!f)
return null;
else
return f.read();
}
write(file: string, content: string) {
var totalDelta = 0;
var file = normalizePath(file);
var f = this._byPath[file];
if (content === null) {
// removal
if (f) {
totalDelta -= f.contentLength;
f.node.parentElement.removeChild(f.node);
delete this._byPath[file];
}
}
else {
// addition
if (f) {
var lengthBefore = f.contentLength;
f.write(content);
totalDelta += f.contentLength - lengthBefore;
}
else {
var comment = document.createComment('');
var f = new DOMFile(comment, file, null, 0, 0);
f.write(content);
this._document.body.appendChild(f.node);
totalDelta += f.contentLength;
}
}
this._totals.timestamp = this.timestamp;
this._totals.updateNode();
}
}
export module DOMDrive {
export interface DocumentSubset {
body: HTMLBodyElementSubset;
createComment(data: string): Comment;
}
export interface HTMLBodyElementSubset {
appendChild(node: Node);
insertBefore(newChild: Node, refNode?: Node);
firstChild: Node;
}
}
}module persistence.dom {
export class DOMFile {
private _encodedPath: string = null;
constructor(
public node: Comment,
public path: string,
private _encoding: (text: string) => any,
private _contentOffset: number,
public contentLength: number) {
}
static tryParse(cmheader: CommentHeader): DOMFile {
// /file/path/continue
// "/file/path/continue"
// /file/path/continue [encoding]
var parseFmt = /^\s*((\/|\"\/)(\s|\S)*[^\]])\s*(\[((\s|\S)*)\])?\s*$/;
var parsed = parseFmt.exec(cmheader.header);
if (!parsed) return null; // does not match the format
var filePath = parsed[1];
var encodingName = parsed[5];
if (filePath.charAt(0) === '"') {
if (filePath.charAt(filePath.length - 1) !== '"') return null; // unpaired leading quote
try {
if (typeof JSON !== 'undefined' && typeof JSON.parse === 'function')
filePath = JSON.parse(filePath);
else
filePath = eval(filePath); // security doesn't seem to be compromised, input is coming from the same file
}
catch (parseError) {
return null; // quoted path but wrong format (JSON expected)
}
}
var encoding = encodings[encodingName || 'LF'];
// invalid encoding considered a bogus comment, skipped
if (encoding)
return new DOMFile(cmheader.node, filePath, encoding, cmheader.contentOffset, cmheader.contentLength);
return null;
}
read() {
// proper HTML5 has substringDate to read only a chunk
// (that saves on string memory allocations
// comparing to fetching the whole text including the file name)
var contentText = typeof this.node.substringData === 'function' ?
this.node.substringData(this._contentOffset, 1000000000) :
this.node.nodeValue.slice(this._contentOffset);
// XML end-comment is escaped when stored in DOM,
// unescape it back
var restoredText = contentText.replace(/\-\-\*(\**)\>/g, '--*$1>');
// decode
var decodedText = this._encoding(restoredText);
// update just in case it's been off
this.contentLength = decodedText.length;
return decodedText;
}
write(content: any) {
var encoded = bestEncode(content);
var protectedText = encoded.content.replace(/\-\-(\**)\>/g, '--*$1>');
if (!this._encodedPath) {
// most cases path is path,
// but if enything is weird, it's going to be quoted
// (actually encoded with JSON format)
var encp = bestEncode(this.path, true /*escapePath*/);
this._encodedPath = encp.content;
}
var leadText = ' ' + this._encodedPath + (encoded.encoding === 'LF' ? '' : ' [' + encoded.encoding + ']') + '\n';
this.node.nodeValue = leadText + encoded.content;
this.contentLength = content.length;
}
}
}module persistence.dom {
export class DOMTotals {
constructor(
public timestamp: number,
public totalSize: number,
private _node: Comment) {
}
static tryParse(cmheader: CommentHeader): DOMTotals {
// TODO: preserve unknowns when parsing
var parts = cmheader.header.split(',');
var anythingParsed = false;
var totalSize = 0;
var timestamp = 0;
for (var i = 0; i < parts.length; i++) {
// total 234Kb
// total 23
// total 6Mb
var totalFmt = /^\s*total\s+(\d*)\s*([KkMm])?b?\s*$/;
var totalMatch = totalFmt.exec(parts[i]);
if (totalMatch) {
try {
var total = parseInt(totalMatch[1]);
if ((totalMatch[2] + '').toUpperCase() === 'K')
total *= 1024;
else if ((totalMatch[2] + '').toUpperCase() === 'M')
total *= 1024 * 1024;
totalSize = total;
anythingParsed = true;
}
catch (totalParseError) { }
continue;
}
var savedFmt = /^\s*saved\s+(\d+)\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d+)\s+(\d+)\:(\d+)(\:(\d+(\.(\d+))?))\s*$/i;
var savedMatch = savedFmt.exec(parts[i]);
if (savedMatch) {
var saveDate = savedMatch[1];
// 25 Apr 2015 22:52:01.231
try {
var savedDay = parseInt(savedMatch[1]);
var savedMonth = ('Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec').toUpperCase().split('|').indexOf(savedMatch[2]) + 1;
var savedYear = parseInt(savedMatch[3]);
if (savedYear < 100)
savedYear += 2000; // no 19xx notation anymore :-(
var savedHour = parseInt(savedMatch[4]);
var savedMinute = parseInt(savedMatch[5]);
var savedSecond = savedMatch[7] ? parseFloat(savedMatch[7]) : 0;
timestamp = new Date(savedYear, savedMonth, savedDay, savedHour, savedMinute, savedSecond | 0).valueOf();
timestamp += savedSecond - (savedSecond | 0);
anythingParsed = true;
}
catch (savedParseError) { }
}
}
if (anythingParsed)
return new DOMTotals(timestamp, totalSize, cmheader.node);
else
return null;
}
updateNode() {
// TODO: update the node content
// total 4Kb, saved 25 Apr 2015 22:52:01.231
var newTotals =
'total ' + (
this.totalSize < 1024 * 2 ? this.totalSize + '' :
this.totalSize < 1024 * 1024 * 2 ? ((this.totalSize / 1024) | 0) + 'Kb' :
((this.totalSize / (1024 * 1024)) | 0) + 'Mb') + ', ' +
'saved ';
var saveDate = new Date(this.timestamp);
newTotals +=
saveDate.getFullYear() + ':' +
num2(saveDate.getMonth() + 1) + ':';
function num2(n: number) {
return n <= 9 ? '0' + n : '' + n;
}
}
}
}module persistence.dom {
export function parseDOMStorage(document: parseDOMStorage.DocumentSubset): parseDOMStorage.ContinueParsing {
var loadedFiles: DOMFile[] = [];
var loadedTotals: DOMTotals;
var lastNode: Node;
var loadedSize = 0;
return continueParsing();
function continueParsing(): parseDOMStorage.ContinueParsing {
continueParsingDOM(false);
return {
continueParsing,
finishParsing,
loadedSize,
totalSize: loadedTotals ? loadedTotals.totalSize : 0,
loadedFileCount: loadedFiles.length
};
}
function finishParsing(): DOMDrive {
continueParsingDOM(true);
if (loadedTotals) {
loadedTotals.totalSize = loadedSize;
loadedTotals.updateNode();
}
var drive = new DOMDrive(loadedTotals, loadedFiles, document);
return drive;
}
function continueParsingDOM(finish: boolean) {
if (document.body) {
if (!lastNode)
lastNode = document.body.firstChild;
while (true) {
if (!lastNode) return;
else if (!finish && lastNode == document.body.lastChild) return;
if (lastNode.nodeType === 8) {
processNode(<Comment>lastNode);
}
lastNode = lastNode.nextSibling;
}
}
}
function processNode(node: Comment): boolean {
var cmheader = new CommentHeader(node);
var file = DOMFile.tryParse(cmheader);
if (file) {
loadedFiles.push(file);
loadedSize += file.contentLength;
return true;
}
var totals = DOMFile.tryParse(cmheader);
}
}
export module parseDOMStorage {
export interface ContinueParsing {
continueParsing(): ContinueParsing;
finishParsing(): DOMDrive;
loadedFileCount: number;
loadedSize: number;
totalSize: number;
}
export interface DocumentSubset extends DOMDrive.DocumentSubset {
body: HTMLBodyElementSubset;
}
export interface HTMLBodyElementSubset extends DOMDrive.HTMLBodyElementSubset {
lastChild: Node;
}
}
}module persistence.encodings {
export function CR(text: string): string {
return text.
replace(/\r\n|\n/g, '\r').
replace(/\-\-\*(\**)\>/g, '--$1>');
}
}module persistence.encodings {
export function CRLF(text: string): string {
return text.
replace(/\r|\n/g, '\r\n').
replace(/\-\-\*(\**)\>/g, '--$1>');
}
}module persistence.encodings {
export function LF(text: string): string {
return text.
replace(/\r\n|\r/g, '\n').
replace(/\-\-\*(\**)\>/g, '--$1>');
}
}module persistence.encodings {
export function base64(text: string): any {
// TODO: convert from base64 to text
// TODO: invent a prefix to signify binary data
throw new Error('Base64 encoding is not implemented yet.');
}
}module persistence.encodings {
export function eval(text: string): any {
return (0, eval)(text);
}
}module persistence.encodings {
export function json(text: string): any {
var result = typeof JSON ==='undefined' ? eval(text) : JSON.parse(text);
if (result && typeof result !== 'string' && result.type) {
var ctor: any = window[result.type];
result = new ctor(result);
}
return result;
}
}module persistence {
export interface Drive {
timestamp: number;
files(): string[];
read(file: string): string;
write(file: string, content: string);
}
export module Drive {
export interface Shadow {
timestamp: number;
write(file: string, content: string): void;
}
export interface Optional {
name: string;
detect(uniqueKey: string, callback: (detached: Detached) => void): void;
}
export interface Detached {
timestamp: number;
totalSize?: number;
applyTo(mainDrive: Drive, callback: Detached.CallbackWithShadow): void;
purge(callback: Detached.CallbackWithShadow): void;
}
export module Detached {
export interface CallbackWithShadow {
(loaded: Shadow): void;
progress?: (current: number, total: number) => void;
}
}
}
}module persistence {
export function bestEncode(content: any, escapePath?: boolean): { content: string; encoding: string; } {
if (content.length>1024*16) {
// TODO: consider packing tightly and using eval encoding to unpack
}
if (typeof content!=='string')
return { content: encodeArrayOrSimilarAsJSON(content), encoding: 'json' };
var needsEscaping: boolean;
if (escapePath) {
// zero-char, newlines, leading/trailing spaces, quote and apostrophe
needsEscaping = /\u0000|\r|\n|^\s|\s$|\"|\'/.test(content);
}
else {
needsEscaping = /\u0000|\r/.test(content);
}
if (needsEscaping) {
// ZERO character is officially unsafe in HTML,
// CR is contentious in IE (which converts any CR or LF into CRLF)
return { content: encodeUnusualStringAsJSON(content), encoding: 'json' };
}
else {
return { content: content, encoding: 'LF' };
}
}
function encodeUnusualStringAsJSON(content: string): string {
if (typeof JSON !== 'undefined' && typeof JSON.stringify === 'function') {
var simpleJSON = JSON.stringify(content);
var sanitizedJSON = simpleJSON.
replace(/\u0000/g, '\\u0000').
replace(/\r/g, '\\r').
replace(/\n/g, '\\n');
return sanitizedJSON;
}
else {
var result = content.replace(
/\"\u0000|\u0001|\u0002|\u0003|\u0004|\u0005|\u0006|\u0007|\u0008|\u0009|\u00010|\u00011|\u00012|\u00013|\u00014|\u00015|\u0016|\u0017|\u0018|\u0019|\u0020|\u0021|\u0022|\u0023|\u0024|\u0025|\u0026|\u0027|\u0028|\u0029|\u0030|\u0031/g,
(chr) =>
chr === '\t' ? '\\t' :
chr === '\r' ? '\\r' :
chr === '\n' ? '\\n' :
chr === '\"' ? '\\"' :
chr < '\u0010' ? '\\u000' + chr.charCodeAt(0).toString(16) :
'\\u00' + chr.charCodeAt(0).toString(16));
return result;
}
}
function encodeArrayOrSimilarAsJSON(content: any): string {
var type = content instanceof Array ? null : content.constructor.name || content.type;
if (typeof JSON !== 'undefined' && typeof JSON.stringify === 'function') {
if (type) {
var wrapped = { type, content };
var wrappedJSON = JSON.stringify(wrapped);
return wrappedJSON;
}
else {
var contentJSON = JSON.stringify(content);
return contentJSON;
}
}
else {
var jsonArr: string[] = [];
if (type) {
jsonArr.push('{"type": "');
jsonArr.push(content.type || content.prototype.constructor.name);
jsonArr.push('", "content": [');
}
else {
jsonArr.push('[');
}
for (var i = 0; i < content.length; i++) {
if (i) jsonArr.push(',');
jsonArr.push(content[i]);
}
if (type)
jsonArr.push(']}');
else
jsonArr.push(']');
return jsonArr.join('');
}
}
}module persistence {
// TODO: pass in progress callback
export function bootMount(uniqueKey: string, document: Document): bootMount.ContinueLoading {
var continueParse: persistence.dom.parseDOMStorage.ContinueParsing;
var ondomdriveloaded;
var domDriveLoaded: Drive;
var storedFinishCallback;
mountDrive(
callback => {
if (domDriveLoaded)
callback(domDriveLoaded);
else
ondomdriveloaded = callback;
},
uniqueKey,
[attached.indexedDB, attached.webSQL, attached.localStorage],
mountedDrive => {
storedFinishCallback(mountedDrive);
});
return continueLoading();
function continueLoading(): bootMount.ContinueLoading {
continueDOMLoading();
// TODO: record progress
return { continueLoading, finishLoading };
}
function finishLoading(finishCallback: (monutedDrive: Drive) => void) {
storedFinishCallback = finishCallback;
continueDOMLoading();
domDriveLoaded = continueParse.finishParsing();
if (ondomdriveloaded) {
ondomdriveloaded(domDriveLoaded);
}
}
function continueDOMLoading() {
continueParse = continueParse ? continueParse.continueParsing() : dom.parseDOMStorage(document);
}
}
module bootMount {
export interface ContinueLoading {
continueLoading(): ContinueLoading;
finishLoading(finishCallback: (mountedDrive: Drive) => void);
}
}
}module persistence {
export function mountDrive(
loadDOMDrive: (callback: (dom: Drive) => void)=> void,
uniqueKey: string,
optionalModules: Drive.Optional[],
callback: mountDrive.Callback): void {
var driveIndex = 0;
loadNextOptional();
function loadNextOptional() {
while (driveIndex < optionalModules.length &&
(!optionalModules[driveIndex] || typeof optionalModules[driveIndex].detect !== 'function')) {
driveIndex++;
}
if (driveIndex >= optionalModules.length) {
loadDOMDrive(dom => callback(new MountedDrive(dom, null)));
return;
}
var op = optionalModules[driveIndex];
op.detect(
uniqueKey,
detached => {
if (!detached) {
driveIndex++;
loadNextOptional();
return;
}
loadDOMDrive(dom => {
if (detached.timestamp > dom.timestamp) {
var callbackWithShadow: Drive.Detached.CallbackWithShadow = loadedDrive => {
dom.timestamp = detached.timestamp;
callback(new MountedDrive(dom, loadedDrive));
};
if (callback.progress)
callbackWithShadow.progress = callback.progress;
loadDOMDrive(dom => detached.applyTo(dom, callbackWithShadow));
}
else {
var callbackWithShadow: Drive.Detached.CallbackWithShadow = loadedDrive => {
callback(new MountedDrive(dom, loadedDrive));
};
if (callback.progress)
callbackWithShadow.progress = callback.progress;
detached.purge(callbackWithShadow);
}
});
});
}
}
export module mountDrive {
export interface Callback {
(drive: Drive): void;
progress?: (current: number, total: number) => void;
}
}
class MountedDrive implements Drive {
timestamp: number = 0;
constructor (private _dom: Drive, private _shadow: Drive.Shadow) {
this.timestamp = this._dom.timestamp;
}
files(): string[] {
return this._dom.files();
}
read(file: string): string {
return this._dom.read(file);
}
write(file: string, content: string) {
this._dom.timestamp = this.timestamp;
this._dom.write(file, content);
if (this._shadow) {
this._shadow.timestamp = this.timestamp;
this._shadow.write(file, content);
}
}
}
}module persistence {
export function normalizePath(path: string) : string {
if (!path) return '/'; // empty paths converted to root
while (' \n\t\r'.indexOf(path.charAt(0))>=0) // removing leading whitespace
path = path.slice(1);
while ('\n\t\r\\'.indexOf(path.charAt(path.length - 1))>=0) // removing trailing whitespace and trailing slashes
path = path.slice(0, path.length - 1);
if (path.charAt(0) !== '/') // ensuring leading slash
path = '/' + path;
path = path.replace(/\/\/*/g, '/'); // replacing duplicate slashes with single
return path;
}
}declare function openDatabase(
name: string,
version: any,
displayName: string,
size: number,
upgrade?: DatabaseCallback): Database;
interface DatabaseCallback {
(database: Database): void;
}
interface Database {
transaction(
callback: (transaction: SQLTransaction) => void,
errorCallback?: (error: SQLError) => void,
successCallback?: () => void);
readTransaction(
callback: (transaction: SQLTransaction) => void,
errorCallback?: (error: SQLError) => void,
successCallback?: () => void);
version: string;
changeVersion(
oldVersion: string,
newVersion: string,
callback: (transaction: SQLTransaction) => void,
errorCallback?: (error: SQLError) => void,
successCallback?: () => void);
}
interface SQLTransaction {
executeSql(
sqlStatement: string,
arguments?: any[],
callback?: (transaction: SQLTransaction, result: SQLResultSet) => void,
errorCallback?: (transaction: SQLTransaction, error: SQLError) => void): void;
}
interface SQLError {
/**
* UNKNOWN_ERR = 0;
* DATABASE_ERR = 1;
* VERSION_ERR = 2;
* TOO_LARGE_ERR = 3;
* QUOTA_ERR = 4;
* SYNTAX_ERR = 5;
* CONSTRAINT_ERR = 6;
* TIMEOUT_ERR = 7;
*/
code: number;
message: string
}
interface SQLResultSet {
insertId: number;
rowsAffected: number;
rows: SQLResultSetRowList;
}
interface SQLResultSetRowList {
length: number;
item(index: number): any;
}<!doctype html>
<title>mini shell </title>
<script data-legit=mi>
<%=embedFile('boot/onerror.js')%>
//# sourceURL=boot/onerror.js
</script>
<script data-legit=mi>
earlyBoot();
<%=embedFile('boot/base.js')%>
<%=embedFile('boot/earlyBoot.js')%>
<%=embedFile('boot/bootUI.js')%>
//# sourceURL=boot/*.js
</script>
<script data-legit=mi>
<%=typescriptBuild()%>
//# sourceURL=typescriptBuild.ts
</script>
<!-- total 56Mb, saved 25 Apr 2015 22:52:01.231 -->
<!-- /LF-file.txt
123-->
<!-- /src/LF-file.txt
123-->
<!-- /src1/LF-file.txt
123-->
<!-- /CRLF-file (CRLF)
123
4-->
<!-- /eval-file.txt (eval)
"new ok" + String.fromCharCode(Math.random()*16000)+" and what\r\n or "+String.fromCharCode(1)+"?"-->
<!-- /json-file.txt (json)
"new ok \u2222 and what\r\n or \u0001?"-->
<!-- /runme.js
console.log(123)-->#Mini-portable shell - new
Self-editing filesystem embedded in a single HTML file.
The idea, all of the painstaking implementation and the vision by Oleg Mihailik. See the credits section for the used libraries and respective licences.